/* 
 * File:   RapidConfigManager.cpp
 * Author: dell
 * 
 * Created on 2012年4月2日, 上午11:36
 */

#include <stdlib.h>
#include <ext/hash_map>
#include <sys/stat.h>
#include <sys/file.h>
#include "RapidConfigManager.h"
#include "../dom/XMLDocument.h"
#include "../dom/XMLElement.h"
#include "../dom/XMLAttribute.h"

#include "ServerConfig.h"
#include "../StringUtils.h"

using namespace std;
using namespace Try;

namespace Rapid{

    RapidConfigManager::RapidConfigManager() {
    }

    RapidConfigManager::~RapidConfigManager() {

        vector<ServerConfig*>::iterator scfgit = serverConfigs.begin();
        for(; scfgit != serverConfigs.end(); scfgit++){
            delete *scfgit;
        }
        serverConfigs.clear();

    }

    bool RapidConfigManager::loadConfig(const char* globalConfigFileName, const char* serverConfigFileName){
        LOG_DEBUG("");
        if(!globalDoc.loadFile(globalConfigFileName)){
            LOG_ERROR("load %s failure", globalConfigFileName);
            return false;
        }
        if(!serverDoc.loadFile(serverConfigFileName)){
            LOG_ERROR("load %s failure", serverConfigFileName);
            return false;
        }    

        //解析server配置文件
        XMLElement* globalRoot = globalDoc.getRootElement();
        if(!globalRoot){
            LOG_ERROR("the root element does not exist, in %s", globalConfigFileName);
            return false;
        }    

        //解析server配置文件
        XMLElement* serverRoot = serverDoc.getRootElement();
        if(!serverRoot){
            LOG_ERROR("the root element does not exist, in %s", serverConfigFileName);
            return false;
        }

        vector<XMLElement*> serverELs;
        serverRoot->getChildren("server", &serverELs);
        LOG_DEBUG("server config count:%d", serverELs.size());
        vector<XMLElement*>::iterator selit = serverELs.begin();
        for(; selit != serverELs.end(); selit++){
            XMLElement* serverCfg = *selit;
            ServerConfig* serverConfig = new ServerConfig();
            serverConfigs.push_back(serverConfig);

            const char* portCfg = serverCfg->getAttributeValue("port");
            if(!portCfg){
                LOG_ERROR("port not exist, in %s server node", serverConfigFileName);
                return false;
            }
            serverConfig->port = atoi(portCfg);

            const char* workerCountCfg = serverCfg->getAttributeValue("workerCount");
            if(workerCountCfg){
                serverConfig->workerCount = atoi(workerCountCfg);
            }

            const char* requestBufferSizeCfg = serverCfg->getAttributeValue("requestBufferSize");
            if(requestBufferSizeCfg){
                serverConfig->requestBufferSize = atoi(requestBufferSizeCfg);
            }

            const char* responseBufferSizeCfg = serverCfg->getAttributeValue("responseBufferSize");
            if(responseBufferSizeCfg){
                serverConfig->responseBufferSize = atoi(responseBufferSizeCfg);
            }

            const char* requestLineMaxBytesCfg = serverCfg->getAttributeValue("requestLineMaxBytes");
            if(requestLineMaxBytesCfg){
                serverConfig->requestLineMaxBytes = atoi(requestLineMaxBytesCfg);
            }

            const char* timeoutCfg = serverCfg->getAttributeValue("timeout");
            if(timeoutCfg){
                serverConfig->timeout = atof(timeoutCfg)/1000.0;
            }

            const char* poolMaxHandlerCountCfg = serverCfg->getAttributeValue("poolMaxHandlerCount");
            if(poolMaxHandlerCountCfg){
                serverConfig->poolMaxHandlerCount = atoi(poolMaxHandlerCountCfg);
            }        

            if(!buildContextConfigs(serverConfig, serverCfg)){
                return false;
            }

            if(!buildEngineConfig(serverConfig, serverCfg)){
                return false;
            }

            if(!buildCacheConfig(serverConfig, serverCfg)){
                return false;
            }
            
            serverConfig->debug();

        }

        return true;
    }

    std::vector<ServerConfig*>* RapidConfigManager::getServerConfigs(){
        return &serverConfigs;
    }


    bool RapidConfigManager::buildEngineConfig(ServerConfig* serverConfig, Try::XMLElement* serverCfg){
    //		<engine name="flvservice" lib="/usr/local/lib/flvservice.so">
    //		    <createMethod name="create" />
    //			<destroyMethod name="destroy" />
    //		</engine>
        XMLElement* engineCfg = serverCfg->getChild("engine");
        if(engineCfg){

            const char* engineName = engineCfg->getAttributeValue("name");
            if(!engineName || !strlen(engineName)){
                LOG_ERROR("engine \"name\" is empty");
                return false;
            }

            const char* lib = engineCfg->getAttributeValue("lib");
            if(!lib || !strlen(lib)){
                LOG_ERROR("engine \"lib\" is empty");
                return false;
            }

            XMLElement* createMethodCfg = engineCfg->getChild("createMethod");
            if(!createMethodCfg){
                LOG_ERROR("engine \"createMethod\" node is not exist");
                return false;
            }
            const char* createMethodName = createMethodCfg->getAttributeValue("name");
            if(!createMethodName || !strlen(createMethodName)){
                LOG_ERROR("engine createMethod \"name\" is empty");
                return false;
            }
            const char* createMethodParams = createMethodCfg->getAttributeValue("params");


            XMLElement* destroyMethodCfg = engineCfg->getChild("destroyMethod");
            if(!destroyMethodCfg){
                LOG_ERROR("engine \"destroyMethod\" node is not exist");
                return false;
            }
            const char* destroyMethodName = destroyMethodCfg->getAttributeValue("name");
            if(!destroyMethodName || !strlen(destroyMethodName)){
                LOG_ERROR("engine destroyMethod \"name\" is empty");
                return false;
            }
            const char* destroyMethodParams = destroyMethodCfg->getAttributeValue("params");


            serverConfig->existEngineConfig = true;
            serverConfig->engineConfig.name.assign(engineName);
            serverConfig->engineConfig.lib.assign(lib);
            serverConfig->engineConfig.createMethod.assign(createMethodName);
            if(createMethodParams){
                serverConfig->engineConfig.createMethodParams.assign(createMethodParams);
            }
            serverConfig->engineConfig.destroyMethod.assign(destroyMethodName);
            if(destroyMethodParams){
                serverConfig->engineConfig.destroyMethodParams.assign(destroyMethodParams);
            }
        }

        return true;
    }


    bool RapidConfigManager::buildContextConfigs(ServerConfig* serverConfig, XMLElement* serverCfg){
        vector<XMLElement*> contextELs;
        serverCfg->getChildren("context", &contextELs);
        LOG_DEBUG("port:%d, context config count:%d", serverConfig->port, contextELs.size());    
        vector<XMLElement*>::iterator cxtit = contextELs.begin();
        for(; cxtit != contextELs.end(); cxtit++){
            XMLElement* contextCfg = *cxtit;

            //context name="/example" docBase="/opt/app/example/webroot" maxSpeed="1048576"

            const char* name = contextCfg->getAttributeValue("name");
            if(!name || strlen(name)==0){
                LOG_ERROR("context \"name\" is empty");
                return false;
            }
            ServerConfig::ContextConfigMap::iterator cxtit = serverConfig->contextConfigMap.find(name);
            if(cxtit != serverConfig->contextConfigMap.end()){
                LOG_ERROR("context \"name\" is duplicate");
                return false;
            }

            ContextConfig* contextConfig = new ContextConfig();
            contextConfig->name.assign(name);

            serverConfig->contextConfigMap.insert(ServerConfig::ContextConfigMap::value_type(contextConfig->name.c_str(), contextConfig));

            const char* docBase = contextCfg->getAttributeValue("docBase");
            if(!docBase || strlen(docBase)==0){
                LOG_ERROR("context \"docBase\" is empty");
                return false;
            }
            contextConfig->docBase.assign(docBase);

            const char* maxSpeed = contextCfg->getAttributeValue("maxSpeed");
            if(maxSpeed){
                contextConfig->maxSpeed = atoi(maxSpeed);
            }

            if(!buildAllWelcomeFiles(serverConfig, contextConfig, contextCfg)){
                return false;
            }

            if(!buildContentTypeConfigs(serverConfig, contextConfig, contextCfg)){
                return false;
            }

        }

        return true;
    }


    bool RapidConfigManager::buildAllWelcomeFiles(ServerConfig* serverConfig, ContextConfig* contextConfig, Try::XMLElement* contextCfg){

        buildWelcomeFiles(serverConfig, contextConfig, contextCfg->getChild("welcome-files"));
        buildWelcomeFiles(serverConfig, contextConfig, globalDoc.getRootElement()->getChild("welcome-files"));

        LOG_DEBUG("contextConfig->welcomeFiles.size:%d", contextConfig->welcomeFiles.size());

        return true;
    }

    bool RapidConfigManager::buildWelcomeFiles(ServerConfig* serverConfig, ContextConfig* contextConfig, XMLElement* welcomeFilesEL){
    //    XMLElement* welcomeFilesEL = contextCfg->getChild("welcome-files");
        if(!welcomeFilesEL){
            LOG_DEBUG("welcome file is not exist");
            return true;
        }

        vector<XMLElement*> wfileELs;
        welcomeFilesEL->getChildren("file", &wfileELs);
        if(wfileELs.empty()){
            LOG_DEBUG("welcome file is not exist");
            return true;
        }
        LOG_DEBUG("port:%d, context:%s, welcome-files.size():%d", serverConfig->port, contextConfig->name.c_str(), wfileELs.size());    
        vector<XMLElement*>::iterator wfileit = wfileELs.begin();
        for(; wfileit != wfileELs.end(); wfileit++){
            XMLElement* wfileCfg = *wfileit;
            const char* wfile = wfileCfg->getText();
            if(wfile && strlen(wfile)){
                if(!containsWelcomeFile(contextConfig, wfile) && existWelcomeFile(contextConfig, wfile)){
                    contextConfig->welcomeFiles.push_back(wfile);
                }
            }else{
                LOG_WARN("welcome file is empty");
            }
        }
        return true;
    }

    bool RapidConfigManager::existWelcomeFile(ContextConfig* contextConfig, const char* wfile){
        string filename = string(contextConfig->docBase).append("/").append(wfile);
        LOG_DEBUG("filename:%s", filename.c_str());
        struct stat wfstat;
        if(stat(filename.c_str(), &wfstat)!=0){
            LOG_DEBUG("welcome file \"%s\" not exist", filename.c_str());
            return false;
        }

        if(S_ISDIR(wfstat.st_mode)){
            LOG_DEBUG("welcome file \"%s\" is dir", filename.c_str());
            return false;
        }    

        return true;
    }

    bool RapidConfigManager::containsWelcomeFile(ContextConfig* contextConfig, const char* wfile){
        std::vector<const char*>::iterator it = contextConfig->welcomeFiles.begin();
        for(; it != contextConfig->welcomeFiles.end(); it++){
            if(strcmp(*it, wfile)==0){
                return true;
            }
        }
        return false;
    }

    bool RapidConfigManager::buildContentTypeConfigs(ServerConfig* serverConfig, ContextConfig* contextConfig, Try::XMLElement* contextCfg){

        //解析全局content-types
        XMLElement* globalRoot = globalDoc.getRootElement();
        XMLElement* globalContentTypesEL = globalRoot->getChild("content-types");
        if(!globalContentTypesEL){
            LOG_DEBUG("global content-types node is not exist");
            return true;
        }

        vector<XMLElement*> gcttELs;
        globalContentTypesEL->getChildren("content-type", &gcttELs);
        if(gcttELs.empty()){
            LOG_DEBUG("global content-types node is not exist");
            return true;
        }

        if(!buildContentTypeConfigs(contextConfig, contextCfg, gcttELs)){
            return false;
        }

        //解析context自己content-types
        XMLElement* serverContentTypesEL = contextCfg->getChild("content-types");
        if(!serverContentTypesEL){
            LOG_DEBUG("server content-types node is not exist");
            return true;
        }

        vector<XMLElement*> scttELs;
        serverContentTypesEL->getChildren("content-type", &scttELs);
        if(scttELs.empty()){
            LOG_DEBUG("server content-types node is not exist");
            return true;
        }

        if(!buildContentTypeConfigs(contextConfig, contextCfg, scttELs)){
            return false;
        }    

        return true;
    }


    bool RapidConfigManager::buildContentTypeConfigs(ContextConfig* contextConfig, Try::XMLElement* contextCfg, vector<XMLElement*>& contentTypeELs ){
        vector<XMLElement*>::iterator gcttit = contentTypeELs.begin();
        for(; gcttit != contentTypeELs.end(); gcttit++){
            XMLElement* gcttCfg = *gcttit;
            //<content-type fileType="001" mimeType="application/x-001" description=""></content-type>
            //<content-type fileType="txt,js,css" mimeType="text/plain"  charset="utf-8" description=""></content-type>
            const char* fileTypeStr = gcttCfg->getAttributeValue("fileType");
            if(!fileTypeStr || !strlen(fileTypeStr)){
                LOG_ERROR("file type is empty");
                return false;
            }

            const char* mimeType = gcttCfg->getAttributeValue("mimeType");
            if(!mimeType || !strlen(mimeType)){
                LOG_ERROR("mime type is empty");
                return false;
            }

            const char* charset = gcttCfg->getAttributeValue("charset");
            const char* description = gcttCfg->getAttributeValue("description");

            vector<const char*> fileTypes;
            char* fileTypeStrDup = StringUtils::split(fileTypes, fileTypeStr, ',');

            ContentTypeConfig* contentTypeConfig;
            vector<const char*>::iterator ctit = fileTypes.begin();
            for(; ctit != fileTypes.end(); ctit++){
                ContextConfig::ContentTypeConfigMap::iterator ctcit = contextConfig->contentTypeConfigMap.find(*ctit);
                if(ctcit == contextConfig->contentTypeConfigMap.end()){
                    contentTypeConfig = new ContentTypeConfig();
                    contentTypeConfig->fileType = *ctit;
                    contentTypeConfig->mimeType = mimeType;
                    contentTypeConfig->contentType = mimeType;
                    if(charset){
                        contentTypeConfig->charset = charset;
                        contentTypeConfig->contentType.append("; ").append(charset);
                    }
                    if(description){
                        contentTypeConfig->description = description;
                    }

                    contextConfig->contentTypeConfigMap.insert(ContextConfig::ContentTypeConfigMap::value_type(contentTypeConfig->fileType.c_str(), contentTypeConfig));
                }else{
                    contentTypeConfig = ctcit->second;
                    contentTypeConfig->mimeType = mimeType;
                    contentTypeConfig->contentType = mimeType;
                    contentTypeConfig->description = description?description:"";
                    if(charset){
                        contentTypeConfig->charset = charset;
                        contentTypeConfig->contentType.append("; ").append(charset);
                    }else{
                        contentTypeConfig->charset.clear();
                    }
                }

                contentTypeConfig->headers.clear();
                buildHeaders(contentTypeConfig, contextCfg, gcttCfg);

            }

            ::free(fileTypeStrDup);

        }
        return true;
    }



    bool RapidConfigManager::buildHeaders(ContentTypeConfig* contentTypeConfig, Try::XMLElement* contextCfg, Try::XMLElement* contentTypeCfg){
        //解析context config headers
        XMLElement* headersEL = contextCfg->getChild("headers");
        if(headersEL){
            vector<XMLElement*> headerELs;
            headersEL->getChildren("header", &headerELs);
            if(!buildHeaders(contentTypeConfig, headerELs)){
                return false;
            }
        }

        //解析content type config headers
        headersEL = contentTypeCfg->getChild("headers");
        if(headersEL){
            vector<XMLElement*> headerELs;
            headersEL->getChildren("header", &headerELs);
            if(!buildHeaders(contentTypeConfig, headerELs)){
                return false;
            }
        }

        return true;
    }

    bool RapidConfigManager::buildHeaders(ContentTypeConfig* contentTypeConfig, vector<Try::XMLElement*>& headerELs){
        vector<XMLElement*>::iterator hit = headerELs.begin();
        for(; hit != headerELs.end(); hit++){
            XMLElement* hCfg = *hit;
            //<header name="header1" value="1" />
            const char* name = hCfg->getAttributeValue("name");
            if(!name || !strlen(name)){
                LOG_ERROR("header name is empty");
                return false;
            }
            const char* value = hCfg->getAttributeValue("value");
            if(!value){
                LOG_ERROR("header value is empty");
                return false;
            }
            contentTypeConfig->headers.add(name, value);
        }
        return true;
    }

    
    bool RapidConfigManager::buildCacheConfig(ServerConfig* serverConfig, Try::XMLElement* serverCfg){
        //<cache base="/dev/shm/example" capacity="1024" limit="1048576"/>
        XMLElement* cacheCfg = serverCfg->getChild("cache");
        if(cacheCfg){

            const char* baseName = cacheCfg->getAttributeValue("base");
            if(!baseName || !strlen(baseName)){
                LOG_ERROR("cache \"base\" is empty");
                return false;
            }

            const char* capacity = cacheCfg->getAttributeValue("capacity");
            if(!capacity || !strlen(capacity)){
                LOG_ERROR("cache \"capacity\" is empty");
                return false;
            }

            const char* limit = cacheCfg->getAttributeValue("limit");
            if(!limit || !strlen(limit)){
                LOG_ERROR("cache \"limit\" is empty");
                return false;
            }
            
            
            serverConfig->cacheConfig.capacity = atof(capacity);
            if(serverConfig->cacheConfig.capacity <= 0){
                LOG_ERROR("cache \"capacity\" is %f", serverConfig->cacheConfig.capacity);
                return false;
            }
            
            serverConfig->cacheConfig.limit = atol(limit);
            if(serverConfig->cacheConfig.limit <= 0){
                LOG_ERROR("cache \"limit\" is %ld", serverConfig->cacheConfig.limit);
                return false;
            }
            
            serverConfig->cacheConfig.base = baseName;
            if(serverConfig->cacheConfig.base[serverConfig->cacheConfig.base.size()-1] == '/'){
                serverConfig->cacheConfig.base = serverConfig->cacheConfig.base.substr(0, serverConfig->cacheConfig.base.size()-1);
            }            
            
            serverConfig->cacheConfig.existCacheConfig = true;
        }
        
        return true;
    }
    
    
}


